#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <pthread.h>
#include "util.hpp"

// 建立索引（正排索引和倒排索引）
namespace NS_Index
{
    struct DocInfo
    {
        std::string _title;    // 标题
        std::string _content;  // 内容
        std::string _url;      // 地址
        uint64_t _doc_id;      // 文档ID
    };

    struct InvertedElem
    {
        int _doc_id;           // 文档ID
        std::string _word;     // 关键字
        int _weight;           // 权重

    public:
        InvertedElem() : _doc_id(-1), _weight(0)
        {}
    };

    class Index
    {
    public:
        // 倒排拉链类型
        typedef std::vector<InvertedElem> InvertedList;
    private:
        // 正排索引的数据结构使用数组，用下标表示文档ID
        std::vector<DocInfo> _forward_index;  // 正排索引
        // 倒排索引一定是一个关键字和一组InvertedElem对象对应
        std::unordered_map<std::string, InvertedList> _inverted_index; // 倒排索引

        //单例
        static Index* _index_ptr;

    private:
        Index() {}
        Index(const Index& index) = delete;
        // Index& operater = (const Index & index) = delete;

    public:
        static Index* GetInstance()
        {
            static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; // 线程锁
            if (_index_ptr == nullptr)
            {
                pthread_mutex_lock(&mtx);
                if (_index_ptr == nullptr)
                {
                    _index_ptr = new Index(); // 构建对象
                }
                pthread_mutex_unlock(&mtx);
            }

            return _index_ptr;
        }

        ~Index() {}

        // 正排索引 -- 根据doc_id获得文档内容
        DocInfo* GetDocInfo(uint64_t doc_id)
        {
            if (doc_id >= _forward_index.size()) {
                std::cerr << "doc_id error out of range" << std::endl;
                return nullptr;
            }
            return &_forward_index[doc_id];
        }

        // 倒排索引 -- 根据关键字word获得倒排拉链
        InvertedList* GetInvertedList(const std::string& word)
        {
            auto iter = _inverted_index.find(word);
            if (iter == _inverted_index.end()) {
                std::cerr << "get invertedlist failed, invaild word" << std::endl;
                return nullptr;
            }
            return &(iter->second);
        }

        // 构建索引 -- 根据格式化后的文件名建立索引
        bool BuildIndex(const std::string& file_path)
        {
            std::ifstream ifs(file_path, std::ios::in | std::ios::binary); // 打开解析后的文件
            if (!ifs.is_open()) {
                std::cerr << "build index error, open " << file_path << " failed" << std::endl;
                return false;
            }

            int cnt = 0;

            std::string line; // 每个文件的内容占一行
            while (getline(ifs, line))
            {
                DocInfo* info = BuildForwardIndex(line); // 建立正排索引
                if (info == nullptr) {
                    std::cerr << "build line error, continue" << std::endl;  // for debug
                    continue;
                }
                BuildInvertedIndex(*info); // 构建倒排索引
                cnt++;
                if (cnt % 50 == 0) {
                    std::cout << "there are " << cnt << " doc index build success" << std::endl;
                }
            }

            return true;
        }
    private:
        DocInfo* BuildForwardIndex(const std::string& line)
        {
            // 解析line字符串
            std::string sep("\3"); // 行内分隔符

            std::vector<std::string> info_result; // 单行解析结果存储位置
            Util::CutString(line, &info_result, sep);

            if (info_result.size() != 3) {
                return nullptr;
            }

            // 切分后放到Docinfo结构体
            DocInfo info;
            info._title = info_result[0];
            info._content = info_result[1];
            info._url = info_result[2];
            info._doc_id = _forward_index.size();

            // 放到正排数组vector中
            _forward_index.push_back(std::move(info));
            return &(_forward_index.back());
        }

        bool BuildInvertedIndex(const DocInfo& info)
        {
            // 词频统计结构体
            struct word_cnt // 针对一个词的数据统计
            {
                int _title_cnt;
                int _content_cnt;

                word_cnt() : _title_cnt(0), _content_cnt(0)
                {}
            };

            std::unordered_map<std::string, word_cnt> word_map; // 暂存词与词频的映射表

            /* 根据文档标题和内容，分词并进行词频统计 */

            // 针对标题分词
            std::vector<std::string> title_words;
            Jieba::SpiltPhrase(info._title, &title_words);
            // 遍历标题，进行词频统计
            for (std::string s : title_words) {
                boost::to_lower(s); // 统一转小写
                word_map[s]._title_cnt++; // 查找对应关键词，将标题次数++
            }

            // 针对内容分词
            std::vector<std::string> content_words;
            Jieba::SpiltPhrase(info._content, &content_words);
            // 遍历内容，进行词频统计
            for (std::string s : content_words) {
                boost::to_lower(s);
                word_map[s]._content_cnt++;
            }

            /* 建立word和倒排拉链的映射 */
#define TITLE_PRI 10
#define CONTENT_PRI 1

            for (auto& word_pair : word_map)
            {
                InvertedElem item;

                // 构建元素
                item._doc_id = info._doc_id;
                item._word = word_pair.first;
                item._weight = TITLE_PRI * word_pair.second._title_cnt +
                    CONTENT_PRI * word_pair.second._content_cnt; // 设置权重

                // 向拉链中添加元素
                _inverted_index[word_pair.first].push_back(std::move(item));
            }

            return true;
        }
    };
    Index* Index::_index_ptr = nullptr;
}